package castledefense.logic.missile;

import java.util.ArrayList;

import castledefense.data.BuildingData;
import castledefense.data.GameData;
import castledefense.game.render.RenderDebuff;
import castledefense.game.render.RenderMissile;
import castledefense.game.render.RenderUnitDeaths;
import castledefense.logic.Map;
import castledefense.logic.Targetable;
import castledefense.sound.SoundHandler;

public class MissileSystem {

	private final Map map;

	private final RenderMissile renderMissile;
	private final RenderDebuff renderDebuff;
	private final RenderUnitDeaths renderUnitDeaths;

	private ArrayList<Missile> missiles = new ArrayList<Missile>(128);
	private ArrayList<Missile> missileHitList = new ArrayList<Missile>(16);

	private double allDamageMultiplier = 1;
	private int allDamageMultiplierCd = 0;

	public MissileSystem(Map map, RenderMissile renderMissile,
			RenderDebuff renderDebuff, RenderUnitDeaths renderUnitDeaths) {
		this.map = map;
		this.renderMissile = renderMissile;
		this.renderDebuff = renderDebuff;
		this.renderUnitDeaths = renderUnitDeaths;
	}

	public ArrayList<Missile> getMissileList() {
		return missiles;
	}

	/** time as milliseconds, bonus amount as multiplier, eg +100% => 2 */
	public void addBonusDamage(int time, double bonusAmount) {
		allDamageMultiplierCd = time;
		allDamageMultiplier *= bonusAmount;
	}

	/**
	 * 
	 * @param elementType
	 *            -1 if no upgrades otherwise from Data.fire etc
	 * @param target
	 *            Unit that missile want to hit
	 * @param startX
	 * @param startY
	 * @param damage
	 *            how much damage missile do when hit occur
	 * @param level
	 *            tower upgrade level
	 */
	public void createMissile(Targetable target, double startX, double startY,
			double damage, int elementType, int level) {
		switch (elementType) {
		case (BuildingData.noBuilding):
			missiles.add(new Missile(target, startX, startY, damage,
					elementType, level));
			break;
		case (GameData.fire):
			missiles.add(new Missile(target, startX, startY, damage,
					elementType, level));
			break;
		case (GameData.water):
			missiles.add(new Missile(target, startX, startY, damage,
					elementType, level));
			break;
		case (GameData.earth):
			missiles.add(new Missile(target, startX, startY, damage,
					elementType, level));
			break;
		case (GameData.air):
			hit(target, (float)startX, (float)startY, damage, elementType, level);
		}

	}

	public void update(int delta) {
		allDamageMultiplierCd = allDamageMultiplierCd > 0 ? allDamageMultiplierCd
				- delta
				: 0;
		if (allDamageMultiplierCd == 0) {
			allDamageMultiplier = 1;
		}
		int size = missiles.size();
		for (int i = 0; i < size; i++) {
			Missile tmp = missiles.get(i);
			if (tmp.update(delta)) {
				missileHitList.add(tmp);
			}
		}

		size = missileHitList.size();
		if (size != 0) {
			for (int i = 0; i < size; i++) {
				Missile missile = missileHitList.get(i);
				hit(missile.target, (float) missile.x, (float) missile.y,
						missile.damage * allDamageMultiplier, missile.type,
						missile.level);
			}
			missiles.removeAll(missileHitList);
			missileHitList.clear();
		}
	}

	protected void hit(Targetable target, float hitX, float hitY, double damage,
			int elementType, int upgradeLevel) {

		if (target.decreaseHealth(damage, elementType)) {
			renderUnitDeaths.addDeath(target.getType(), (float) target.getCenterX(),
					(float) target.getCenterY());
		}

		if (elementType == GameData.noElement) {
			SoundHandler.missileSound();

		} else if (elementType == GameData.earth) {
			final int duration = BuildingData.getUpgradeLevelDuration(
					elementType, upgradeLevel);
			final boolean isStunnedAllready = target.isStun();
			// if stunned
			if (target.stunChance(duration, BuildingData.getUpgradeLevelValue(
					elementType, upgradeLevel))) {
				// if not yet stunned
				if (!isStunnedAllready) {
					renderDebuff.addBlast(target, duration, GameData.earth);
				}

			}
		} else if (elementType == GameData.water) {
			SoundHandler.waterSound();
			final int duration = BuildingData.getUpgradeLevelDuration(
					elementType, upgradeLevel);
			if (!target.isSlow()) {
				renderDebuff.addBlast(target, duration, GameData.water);
			}
			target.slow(duration, BuildingData.getUpgradeLevelValue(elementType,
					upgradeLevel));
			renderMissile.addBlast(hitX, hitY, GameData.water);
		} else if (elementType == GameData.fire) {
			SoundHandler.fireSound();
			final int duration = BuildingData.getUpgradeLevelDuration(
					elementType, upgradeLevel);
			renderMissile.addBlast(hitX, hitY, GameData.fire);
			if (!target.isDot()) {
				renderDebuff.addBlast(target, duration, GameData.fire);
			}
			target.applyDot(duration, 1000, BuildingData.getUpgradeLevelValue(
					elementType, upgradeLevel), elementType);

		} else if (elementType == GameData.air) {
			SoundHandler.lightningSound();
			target.decreaseHealth(BuildingData.getUpgradeLevelValue(elementType,
					upgradeLevel), elementType);

			ArrayList<Targetable> nearUnits = map.getNearUnits(
					(int) target.getCenterX(), (int) target.getCenterY(),
					4, 3);

			int counter = 2;
			ArrayList<Targetable> chainLightTmp = new ArrayList<Targetable>();
			final int size = nearUnits.size();
			for (int i = 0; i < size && counter > 0; i++) {
				Targetable tmp = nearUnits.get(i);
				if (!target.equals(tmp)) {
					tmp.decreaseHealth(
							BuildingData.getUpgradeLevelValue(elementType
									+ counter, upgradeLevel), elementType);
					chainLightTmp.add(tmp);					
					counter--;

				}
			}
			// Initial strikes
			float tmpX = (float) target.getCenterX();
			float tmpY = (float) target.getCenterY();
			renderMissile.create(hitX, hitY, tmpX, tmpY);

			// bounces
			final int size2 = nearUnits.size();
			for (int i = 0; i < size2; i++) {
				Targetable tmp = nearUnits.get(i);
				if (!target.equals(tmp)) {
					renderMissile.create(tmpX, tmpY, (float) tmp.getCenterX(),
							(float) tmp.getCenterY());
					tmpX = (float) tmp.getCenterX();
					tmpY = (float) tmp.getCenterY();
				}
			}
		}
	}

}
